/**
 * Created by lwei on 2017/4/21.
 */

class CipherSql extends require('sql.js').Database{
	private dbPath:string;
	private tempDb:string;
	private pwd:string;
	private algorithm:string;
	
	private tempFile:string;
	private dbFile:string;
	
	private static fs = require('fs');
	private static path = require("path");
	private static CryptoJS = require("crypto"); /*加密库*/
	
	/**
	 *
	 * @param dbPath 数据库文件路径
	 * @param pwd 数据库密码，没有密码则不加密数据库
	 * @param algthm 加密算法，默认 'aes-256-ctr'
	 */
	constructor(dbPath:string, pwd?:string, algthm?:string) {
		algthm = algthm?algthm:'aes-256-ctr';
		
		if(!dbPath){
			throw "必须指定数据库文件";
		}
		
		/**
		 * 如果不存在数据库文件，创建数据库文件父路径
		 */
		if(!CipherSql.fs.existsSync(dbPath)){
			let dir = CipherSql.path.parse(dbPath).dir;
			CipherSql.mkdirs(dir);
			/*创建数据库文件*/
			CipherSql.fs.closeSync(CipherSql.fs.openSync(dbPath, 'w'));
		}
		
		let buffer, dbFile;
		try{
			buffer = CipherSql.fs.readFileSync(dbPath);
		} catch(e) {
			console.error(e.stack);
			throw "数据库文件错误";
		}
		
		if(pwd){
			try{
				/*解密数据库文件*/
				let decipher = CipherSql.CryptoJS.createDecipher(algthm, pwd);
				buffer = Buffer.concat([decipher.update(buffer) , decipher.final()]);
			} catch (e){ /*无法解密数据库文件*/
				console.error(e.stack);
				throw "密码不正确 或者数据库未被加密 或者数据库文件已损坏";
			}
		}
		
		try{
			super(buffer);
		} catch (e){ /*无法识别数据库文件*/
			console.error(e.stack);
			if(pwd){
				throw "数据库文件已损坏";
			} else {
				throw "数据库文件已被被加密或者数据库文件已损坏";
			}
		}
		
		/*测试数据库文件是否可用*/
		try{
			this.exec('SELECT * FROM sqlite_master WHERE type = "table"');
			
			this.dbPath = dbPath;
			this.pwd = pwd;
			this.algorithm = algthm;
			this.dbFile = dbFile;
			
			/**
			 * 创建临时数据库文件
			 * @type {string}
			 */
			this.tempDb = this.dbPath+".temp";
			this.tempFile = CipherSql.fs.openSync(this.tempDb, 'w'); /*锁住文件*/
		} catch (e){
			if(pwd){
				throw "数据库文件已损坏";
			} else {
				throw "数据库文件已被被加密或者数据库文件已损坏";
			}
		}
	}
	
	/**
	 * 数据库在内存中被改变之后，保存到银盘
	 */
	save(){
		/*高版本node可以直接保存，低版本需要buffer*/
		let buffer = new Buffer(super.export());
		
		/*加密之后保存*/
		if(this.pwd){
			let cipher = CipherSql.CryptoJS.createCipher(this.algorithm, this.pwd);
			buffer = Buffer.concat([cipher.update(buffer), cipher.final()]);
		}
		
		/*先写到临时文件，再复制到正式数据库文件*/
		try{
			CipherSql.fs.writeFileSync(this.tempDb, buffer);
			CipherSql.fs.createReadStream(this.tempDb).pipe(CipherSql.fs.createWriteStream(this.dbPath));
		} catch(e){
			console.error(e.stack);
		}
	}
	
	/**
	 * Execute an SQL query, and returns the result.
	 * @param sql
	 * @returns {any}
	 */
	exec(sql:string, params?:any[]):Array<any>{
		let result:any[] = null;
		
		if(!params){
			let rset = super.exec(sql);
			
			/*将resultset 组装成object*/
			if(rset && rset.length>0){
				result = [];
				rset = rset[0];
				rset.values.forEach((t:any, i:any) => {
					let obj:any = {};
					result.push(obj);
					rset.columns.forEach((c:any, j:any)=>{
						obj[c] = t[j];
					})
				});
			}
		} else {
			let stam = super.prepare(sql);
			stam.bind(params);
			
			while(stam.step()){
				if(!result){
					result = [];
				}
				
				result.push(stam.getAsObject());
			}
		}
		
		return result;
	}
	
	/**
	 * 释放文件锁，删除临时文件
	 */
	close(){
		try {
			CipherSql.fs.closeSync(this.tempFile);
			
			/**
			 * 删除临时文件
			 */
			if(CipherSql.fs.existsSync(this.tempDb)){
				CipherSql.fs.unlinkSync(this.tempDb);
			}
		} catch (e){
			console.error(e.stack);
		}
		super.close();
	}
	
	/**
	 * 递归生成目录
	 * @param dirpath
	 */
	private static mkdirs (dirpath:string) {
		if(!CipherSql.fs.existsSync(dirpath)){
			let p = CipherSql.path.parse(dirpath);
			let dir = p.dir;
			CipherSql.mkdirs(dir);
			CipherSql.fs.mkdirSync(dirpath);
			return;
		}
		return;
	}
}